﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using ParserGenerator.GrammarConstruction;

namespace ParserGenerator.ParserConstruction
{
    [Serializable]
    public sealed class ParserAutomatonAnnotationNodesTable : ReadOnlyDictionary<ParserAutomatonState, ParserAutomatonAnnotationNodes>
    {
        private readonly ParserAutomatonInadequacyNodesTable _inadequacyNodes;

        internal ParserAutomatonAnnotationNodesTable(ParserAutomatonStateTable states)
            : base(new Dictionary<ParserAutomatonState, ParserAutomatonAnnotationNodes>())
        {
            if (states == null)
                throw new ArgumentNullException(nameof(states));
            _inadequacyNodes = new ParserAutomatonInadequacyNodesTable();
            int number = 0;
            var cache = states.ItemLookaheadSetsCache;
            var allTokens = new ParserAutomatonItemLookaheadSet();
            allTokens.UnionWith(states.Tokens);
            foreach (ParserAutomatonState state in states)
            {
                if (state.IsConsistent)
                    continue;
                var shiftTokens = state.GetShiftTokens();
                var conflictedTokens = state.GetConflictedTokens(shiftTokens);
                if (conflictedTokens.Count <= 0)
                    continue;
                ParserAutomatonItemLookaheadSets allLookaheads = null;
                var lookaheadSets = states.ReductionLookaheadSets[state];
                foreach (GrammarTerminalSymbol token in conflictedTokens)
                {
                    bool isPotential = false;
                    var action = new ParserAutomatonAction(state, token);
                    var matrix = new ParserAutomatonContributionMatrix();
                    foreach (ParserAutomatonReduction r in state.Reductions.Values)
                    {
                        var lookaheadTokens = lookaheadSets[r.Production];
                        if (lookaheadTokens.Contains(token))
                        {
                            action.Add(r);
                            if (r.Production.IsEmpty)
                            {
                                GrammarNonterminalSymbol lhs = r.Production.Owner;
                                var column = cache.ComputeLalrLhsContributionColumn(state, lhs, token);
                                matrix.Add(r, column);
                                if (column != null)
                                    isPotential = true;
                            }
                            else
                            {
                                matrix.AddValueItem(r, r.Item);
                                isPotential = true;
                            }
                        }
                    }
                    if (isPotential)
                    {
                        if (shiftTokens.Contains(token))
                        {
                            var target = state.Transitions[token];
                            var shift = new ParserAutomatonShift(state, token, target);
                            action.Add(shift);
                            matrix.Add(shift, null);
                        }
                        var n = new ParserAutomatonInadequacyNode(number, action);
                        var node = new ParserAutomatonAnnotationNode(state, n, matrix);
                        if (allLookaheads == null)
                        {
                            allLookaheads = new ParserAutomatonItemLookaheadSets(state);
                            foreach (ParserAutomatonItem item in state.Core)
                                allLookaheads.Add(item, allTokens);
                        }
                        if (node.ComputeDominantContribution(allLookaheads, true) == null)
                        {
                            _inadequacyNodes.Add(state, n);
                            number++;
                            bool succeeded = TryRecord(node);
                            if (!succeeded)
                                throw new InvalidOperationException(string.Format(SR.RecordAnnotationNodeFail, state));
                            AnnotatePredecessors(node);
                        }
                    }
                }
            }
        }

        private void AnnotatePredecessors(ParserAutomatonAnnotationNode node)
        {
            if (node == null)
                throw new ArgumentNullException(nameof(node));
            var cache = node.State.Table.ItemLookaheadSetsCache;
            var token = node.InadequacyNode.ConflictedToken;
            foreach (ParserAutomatonState state in node.State.Predecessors)
            {
                bool isPotential = false;
                var matrix = new ParserAutomatonContributionMatrix();
                ParserAutomatonItemLookaheadSets lookaheadSets = null;
                foreach (var c in node.Contributions)
                {
                    if (node.IsAlwaysContribution(c.Key))
                        continue;
                    var column = new ParserAutomatonContributionColumn();
                    foreach (ParserAutomatonItem item in node.Contributions[c.Key])
                    {
                        if (item.IsAccept)
                        {
                            /* do thing */
                        }
                        else if (item.DotPosition == 1)
                        {
                            GrammarNonterminalSymbol lhs = item.Production.Owner;
                            var set = cache.ComputeLalrLhsContributionColumn(state, lhs, token);
                            if (set != null)
                                column.UnionWith(set);
                            else
                            {
                                column = null;
                                break;
                            }
                        }
                        else
                        {
                            if (cache.LalrItemHasLookahead(state, item.Predecessor, token))
                                column.Add(item.Predecessor);
                        }
                    }
                    matrix.Add(c.Key, column);
                    if (column != null)
                    {
                        foreach (ParserAutomatonItem item in column)
                        {
                            isPotential = true;
                            if (lookaheadSets == null)
                                lookaheadSets = new ParserAutomatonItemLookaheadSets(state);
                            lookaheadSets.AddValueItem(item, token, state);
                        }
                    }
                }
                if (isPotential)
                {
                    var anno = new ParserAutomatonAnnotationNode(state, node.InadequacyNode, matrix);
                    if (anno.ComputeDominantContribution(lookaheadSets, true) == null)
                        if (TryRecord(anno))
                            AnnotatePredecessors(anno);
                }
            }
        }

        private bool TryRecord(ParserAutomatonAnnotationNode node)
        {
            if (node == null)
                throw new ArgumentNullException(nameof(node));
            ParserAutomatonAnnotationNodes nodes;
            if (!Dictionary.TryGetValue(node.State, out nodes))
            {
                nodes = new ParserAutomatonAnnotationNodes();
                Dictionary.Add(node.State, nodes);
            }
            return nodes.TryInsert(node);
        }
    }
}
